import { getVarType, hasOwnProperty } from '@/utils/common'
import { validateType, validateValue } from '@/utils/validate'
import type { VarTypes, ExcludeUndefined } from '@/types/common'

/**
 * 验证器
 * @param configFileName
 * @param config
 * @param validateOptions
 */
export default function processConfig<T>(
  configFileName: string,
  config: T,
  validateOptions: ProcessConfigOptions<T>
) {
  _processConfig(configFileName, config, validateOptions)
}

function _processConfig<T>(
  configFileName: string,
  config: T,
  validateOptions: ProcessConfigOptions<T>,
  pathKey = '',
  isArray = false
) {
  Object.keys(validateOptions).forEach((_key) => {
    const key = _key as keyof T & string
    const validateOption = validateOptions[key]
    const value = config[key]
    const exist = (config as object).hasOwnProperty(key) && value !== void 0
    let _pathKey = pathKey
    // 组合键路径
    if (pathKey) {
      if (isArray) {
        _pathKey += `[${key}]`
      } else {
        _pathKey += '.' + key
      }
    } else {
      _pathKey = key
    }

    // 验证必传
    if (validateOption.required && !exist) {
      throw new Error(`${configFileName} -> "${_pathKey}" is required`)
    } else if (!exist) {
      // 默认值
      if (hasOwnProperty(validateOption, 'defaultValue')) {
        config[key] =
          typeof validateOption.defaultValue === 'function'
            ? validateOption.defaultValue(config)
            : validateOption.defaultValue
      }
      return
    }

    // 验证类型
    if (validateOption.type) {
      if (!Array.isArray(validateOption.type)) validateOption.type = [validateOption.type]
      if (!validateType(value, validateOption.type)) {
        throw new TypeError(
          `${configFileName} -> "${_pathKey}" is not ${validateOption.type.map((t) => `"${t}"`).join(' or ')}`
        )
      }
    }

    // 验证值
    if (validateOption.value && !validateValue(value, validateOption.value)) {
      throw new Error(`${configFileName} -> "${_pathKey}" is not ${validateOption.value}`)
    }

    // 正则验证
    if (validateOption.regex && !validateOption.regex.test(value as string)) {
      throw new Error(`${configFileName} -> "${_pathKey}" is not match ${validateOption.regex}`)
    }

    // 验证器验证
    if (validateOption.validator) {
      const res = validateOption.validator(value as any, _pathKey)
      if (typeof res === 'string') {
        throw new Error(`${configFileName} -> "${_pathKey}" ${res}`)
      } else if (!res) {
        throw new Error(`${configFileName} -> "${_pathKey}" is not valid`)
      }
    }

    const objectAndArray = getVarType(value) === 'object' || getVarType(value) === 'array'

    // 验证每一项
    if (validateOption.items && objectAndArray) {
      const newValidateOption: { [k: string | number]: ProcessConfigOptionsItem<any> } = {}
      let isArray
      if ((isArray = Array.isArray(value))) {
        value.forEach((_, index) => {
          // @ts-ignore
          newValidateOption[index] = validateOption.items
        })
      } else {
        Object.keys(value as object).forEach((k) => {
          // @ts-ignore
          newValidateOption[k] = validateOption.items
        })
      }

      _processConfig<typeof value>(
        configFileName,
        value,
        newValidateOption as any,
        _pathKey,
        isArray
      )
    } else if (validateOption.children && objectAndArray) {
      // 验证子项
      if (Array.isArray(value)) {
        value.forEach((_, index) => {
          _processConfig<typeof value>(
            configFileName,
            value[index],
            validateOption.children as any,
            _pathKey + `[${index}]`,
            true
          )
        })
      } else {
        _processConfig<typeof value>(
          configFileName,
          value,
          validateOption.children as any,
          _pathKey,
          false
        )
      }
    }

    // 格式化
    if (validateOption.format) {
      try {
        config[key] = validateOption.format(value)
      } catch (error) {
        const msg = error instanceof Error ? error.message : String(error)
        throw new Error(`${configFileName} -> "${_pathKey}" ${msg}`)
      }
    }
  })
}

export type ProcessConfigOptions<T> = {
  [key in keyof T]: ProcessConfigOptionsItem<T[key]>
}

export interface ProcessConfigOptionsItem<T> {
  required?: boolean
  type?: VarTypes | VarTypes[]
  value?: any[]
  regex?: RegExp
  defaultValue?: any | ((v: T) => any)
  validator?: (v: ExcludeUndefined<T>, ext: string) => boolean | string
  format?: (v: T) => any
  children?: T extends Array<any> ? ProcessConfigOptions<T[0]> : ProcessConfigOptions<T>
  items?: T extends Array<any> ? ProcessConfigOptions<T[0]> : ProcessConfigOptionsItem<any>
}
